window.$$console = jDev.console = {
    
  _buttons: [
    {
      caption: 'clear',
      title: 'clear console',
      action: '$$console.clear();'
    }
  ],
  
  _loggingID: '$$console__logging__',
    
  _outElement: null,
  _cmdElement: null,
  _cmdHeight: 21,

  _buffer: [],
  _bi: -1,
  
  _completing: null,
  _completePrefix: null,
  _completeExpr: null,
  _completeBuffer: null,
  _ci: null,
  
  _completion: {
    window: [
      'jDev', 
      'jProton', 
      'firebug'
    ],
    
    document: [
      'getElementById', 
      'getElementsByTagName'
    ]
  },

  _stack: function(command) {
    $$console._buffer.push(command);
    $$console._bi = $$console._buffer.length;
  },
  
  init: function() {
    $$console._element = document.getElementById('console');
    $$console._outElement = document.getElementById('consoleOut');
    $$console._cmdElement = document.getElementById('commandLine');
    
    $$event.add($$console._cmdElement, 'keydown', $$console.onKeyDown);
    window.onerror = $$console.onError;
  },
  
  draw: function(height, width) {
    if (height) {
      $$console._outElement.style.height = height - $$console._cmdHeight+ 'px';
    }
    if (width) {
      $$console._outElement.style.width = width + 'px';
    }
  },
  
  focus: function() {
    $$console._cmdElement.focus();
  },

  log: function(msg, type) {
    var out = $$console._outElement;
    var className = 'jd_' + (type || 'log');
    
    out.innerHTML += '<div class="jdLogMsg ' + className + '">'+msg+'</div>';      
    out.scrollTop = out.scrollHeight - out.clientHeight;
    
    return $$console._loggingID;
  },
  
  error: function(msg) {
    return $$console.log(msg, 'error');
  },
  
  warn: function(msg) {
    return $$console.log(msg, 'warn');
  },
  
  info: function(msg) {
    return $$console.log(msg, 'info');
  },

  execute: function() {
    var cmd = $$console._cmdElement;
    var command = cmd.value;
    
    $$console._stack(command);
    $$console.log('<span>&gt;&gt;&gt;</span> ' + command, 'cmd');
    
    try {
      var result = $$console.eval(command);
      // evita que seja repetido o log, caso o comando executado
      // já seja um log via linha de comando
      if (result != $$console._loggingID) {
        $$console.log($$console.escapeHTML(result));
      }
    } catch (e) {
      $$console.log(e.message || e, 'error');
    }
    
    cmd.value = '';
  },
  
  clear: function() {
    $$console._outElement.innerHTML = '';
  },
  
  escapeHTML: function(s) {
    return (''+s).replace(/</g, '&lt;').replace(/>/g, '&gt;');
  },
  
  eval: function(command) {
    return eval(command);
  },
  
  prevCommand: function() {
    var cmd = $$console._cmdElement;
    var buffer = $$console._buffer;
    
    if ($$console._bi > 0 && buffer.length > 0) {
      cmd.value = buffer[--$$console._bi];
    }
  },

  nextCommand: function() {
    var cmd = $$console._cmdElement;
    
    var buffer = $$console._buffer;
    var limit = buffer.length -1;
    var i = $$console._bi;
    
    if (i < limit) {
      cmd.value = buffer[++$$console._bi];
    } else if (i == limit) {
      ++$$console._bi;
      cmd.value = '';
    }
  },

  autocomplete: function(reverse) {
    var cmd = $$console._cmdElement;
    var val = cmd.value;
    var buffer, obj, objName, commandBegin, result, prefix;
    
    // if it is the beginning of the completion
    if(!$$console._completing) {
      
      // group1 - command begin
      // group2 - base object
      // group3 - property prefix
      var reObj = /(.*[^_$\w\d\.])?((?:[_$\w][_$\w\d]*\.)*)([_$\w][_$\w\d]*)?$/;
      var r = reObj.exec(val);
      
      // parse command
      if (r[1] || r[2] || r[3]) {
        commandBegin = r[1] || '';
        objName = r[2] || '';
        prefix = r[3] || '';
        
      } else if (val == '') {
        commandBegin = objName = prefix = '';
        
      } else {
        return;
      }
      
      $$console._completing = true;

      // find base object
      if(objName == '') {
        obj = window;
        
      } else {
        objName = objName.replace(/\.$/, '');

        var n = objName.split('.');
        var target = window, o;
        
        for (var i=0, ni; ni = n[i]; i++) {
          if (o = target[ni]) {
            target = o;
          } else {
            target = null;
            break;
          }
        }
        obj = target;
      }
      
      // map base object
      if(obj) {
        $$console._completePrefix = prefix;
        $$console._completeExpr = commandBegin + (objName ? objName + '.' : '');
        $$console._ci = -1;
        
        buffer = $$console._completeBuffer = jDev.isIE ?
            $$console._completion[objName || 'window'] || [] : [];
        
        for(var p in obj) {
          buffer.push(p);
        }
      }

    // if it is the continuation of the last completion
    } else {
      buffer = $$console._completeBuffer;
    }  
    
    if (buffer) {
      prefix = $$console._completePrefix;
      
      var diff = reverse ? -1 : 1;
      
      for(var i=$$console._ci+diff, l=buffer.length, bi; i>=0 && i<l; i+=diff) {
        bi = buffer[i];
        
        if (bi.indexOf(prefix) == 0) {
          $$console._ci = i;
          result = bi;
          break;
        }
      }
    }
    
    if (result) {
      cmd.value = $$console._completeExpr + result;
    }
  },
  
  onError: function(msg, href, lineNo) {
    var html = [];
    
    var lastSlash = href.lastIndexOf("/");
    var fileName = lastSlash == -1 ? href : href.substr(lastSlash+1);
    
    html.push(
        '<span class="errorMessage">', msg, '</span>', 
        '<div class="objectBox-sourceLink">', fileName, ' (line ', lineNo, ')</div>'
      );
    
    $$console.log(html.join(''), "error");
  },
  
  onKeyDown: function(e) {
    e = e || event;
    
    var code = e.keyCode;
    
    /*tab, shift, control, alt*/
    if (code != 9 && code != 16 && code != 17 && code != 18) {
      $$console._completing = false;
    }

    if (code == 13 /* enter */) {
      $$console.execute();
      
    } else if (code == 27 /* ESC */) {
      $$console._cmdElement.value = '';
      
    } else if (code == 38 /* up */) {
      $$console.prevCommand();
      
    } else if (code == 40 /* down */) {
      $$console.nextCommand();
      
    } else if (code == 9 /* tab */) {
      $$console.autocomplete(e.shiftKey);
      
    } else
      return;
    
    $$event.stop(e, true);
  }
};
